/********************************************************************
 * PROJECT: CSAIR ROST PROJECT.
 * SUPPLIER: FOREVER OPEN SOURCE SOFTWARE INC.
 ********************************************************************
 * FILE: Gantt.java
 * (C) Copyright Foliage Team 2013, All Rights Reserved.
 ********************************************************************/
package org.foliage.rcp.ui.widgets;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.TypedListener;
import org.foliage.rcp.ui.custom.GanttGrid;
import org.foliage.rcp.ui.events.GanttEvent;
import org.foliage.rcp.ui.events.IGanttListener;
import org.foliage.rcp.ui.graphics.ColorCache;
import org.foliage.rcp.ui.handlers.GanttJointHandler;
import org.foliage.rcp.ui.handlers.IGanttJointHandler;
import org.foliage.rcp.ui.internal.proxy.IScrollBarProxy;
import org.foliage.rcp.ui.internal.proxy.NullScrollBarProxy;
import org.foliage.rcp.ui.internal.proxy.ScrollBarProxy;
import org.foliage.rcp.ui.internal.thread.GanttThread;
import org.foliage.rcp.ui.internal.tooltip.GanttTooltip;
import org.foliage.rcp.ui.preference.DefaultGanttColorManager;
import org.foliage.rcp.ui.preference.DefaultGanttPaintManager;
import org.foliage.rcp.ui.preference.DefaultGanttSettings;
import org.foliage.rcp.ui.preference.GanttMeasure;
import org.foliage.rcp.ui.preference.IGanttColorManager;
import org.foliage.rcp.ui.preference.IGanttPaintManager;
import org.foliage.rcp.ui.preference.IGanttSettings;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class Gantt extends Canvas {

	// ----------------------------------------------- 配置参数属性

	/** 应用的全局控件参数。 */
	private int usedStyle = 0;
	/** 选择模型开关（单选或多选）。 */
	private int selectionType = 0;
	/** 标尺绘制标识。 */
	private boolean headerVisible = false;

	// ----------------------------------------------- 内部适配属性

	/** 甘特图控件适配器。 */
	private IGanttSettings settings = null;
	/** 甘特图绘画适配器。 */
	private IGanttPaintManager paintManager = null;
	/** 甘特图颜色适配器。 */
	private IGanttColorManager colorManager = null;
	/** 拖拽来源控件处理器。 */
	private DragSource dragSource = null;
	/** 拖拽目标控件处理器。 */
	private DropTarget dropTarget = null;
	/** 甘特图线程处理器。 */
	private GanttThread ganttThread = null;
	/** 甘特图监听器数组。 */
	private List ganttListeners = new ArrayList();
	/** 甘特图联合驱动接口。 */
	private IGanttJointHandler jointHandler;

	// ----------------------------------------------- 面板控件属性

	/** 面板纵向滚动条代理。 */
	private IScrollBarProxy vScroll = null;
	/** 面板横向滚动条代理。 */
	private IScrollBarProxy hScroll = null;

	// ----------------------------------------------- 面板内容属性

	/** 甘特图任务链列表。 */
	private List items = new ArrayList();
	/** 甘特图显示的任务链列表。 */
	private List visibleItems = new ArrayList();
	/** 甘特图延迟加载的任务链列表。 */
	private List lazyItems = new ArrayList();
	/** 甘特图任务列表。 */
	private List tasks = new ArrayList();
	/** 甘特图延迟加载的任务列表。 */
	private List lazyTasks = new ArrayList();
	/** 甘特图选中的任务列表。 */
	private List selectedTasks = new ArrayList();
	/** 甘特图范围面板列表。 */
	private List ranges = new ArrayList();

	// ----------------------------------------------- 内部全局变量

	/** 控件显示区域。 */
	private Rectangle displayBounds = null;
	/** 控件显示下限时间（显示区域左端时间）。 */
	private Calendar lowerCalendar = null;
	/** 控件显示上限时间（显示区域右端时间）。 */
	private Calendar upperCalendar = null;
	/** 显示的顶部行序列（显示区域最顶端的行序列）。 */
	private int topIndex = 0;

	// ----------------------------------------------- 多态对象构造器

	public Gantt(GanttGrid parent, int style) {
		this(parent, style, null, null, null);
	}

	public Gantt(GanttGrid parent, int style, IGanttSettings settings, IGanttColorManager colorManager, IGanttPaintManager paintManager) {
		super(parent, checkStyle(style));
		// TODO(deathknight0718@gmail.com): 内置适配器对象赋值。
		this.settings = settings != null ? settings : new DefaultGanttSettings();
		this.colorManager = colorManager != null ? colorManager : new DefaultGanttColorManager();
		this.paintManager = paintManager != null ? paintManager : new DefaultGanttPaintManager();
		this.jointHandler = new GanttJointHandler(new Gantt[] { this });
		// TODO(deathknight0718@gmail.com): 内置全局变量赋值。
		usedStyle = checkStyle(style);
		selectionType = Math.max(usedStyle & SWT.MULTI, usedStyle & SWT.SINGLE);
		// TODO(deathknight0718@gmail.com): 内置时间变量赋值。
		lowerCalendar = Calendar.getInstance(settings.getTimeZone(), settings.getLocale());
		{
			// TODO(deathknight0718@gmail.com): 时间变量起始时间设置为天。
			lowerCalendar.set(Calendar.HOUR_OF_DAY, 0);
			lowerCalendar.set(Calendar.MINUTE, 0);
			lowerCalendar.set(Calendar.SECOND, 0);
			lowerCalendar.set(Calendar.MILLISECOND, 0);
		}
		// TODO(deathknight0718@gmail.com): 内置控件初始化。
		hScroll = (style & SWT.H_SCROLL) == SWT.H_SCROLL ? new ScrollBarProxy(getHorizontalBar()) : new NullScrollBarProxy();
		vScroll = (style & SWT.V_SCROLL) == SWT.V_SCROLL ? new ScrollBarProxy(getVerticalBar()) : new NullScrollBarProxy();
		hScroll.setSelection((hScroll.getMaximum() - hScroll.getMinimum() - hScroll.getThumb()) / 2);
		// TODO(deathknight0718@gmail.com): 拖动功能初始化。
		dragSource = new DragSource(this, DND.DROP_MOVE);
		dropTarget = new DropTarget(this, DND.DROP_MOVE);
		// TODO(deathknight0718@gmail.com): 设置今日范围标识。
		Calendar lower = Calendar.getInstance();
		{
			lower.set(Calendar.HOUR_OF_DAY, 0);
			lower.set(Calendar.MINUTE, 0);
			lower.set(Calendar.SECOND, 0);
			lower.set(Calendar.MILLISECOND, 0);
		}
		Calendar upper = Calendar.getInstance();
		upper.setTime(lower.getTime());
		upper.add(Calendar.DAY_OF_MONTH, 1);
		ranges.add(new GanttRange(this, lower, upper));
		// TODO(deathknight0718@gmail.com): 内置驱动初始化。
		initListeners();
		initThread();
	}

	// ----------------------------------------------- 私有参数效验器

	static int checkStyle(int style) {
		// TODO(deathknight0718@gmail.com): 采用无背景重绘，双缓冲机制，有效降低重绘闪烁。
		style |= SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED;
		return style;
	}

	// ----------------------------------------------- 私有驱动构造器

	void initListeners() {
		// TODO(deathknight0718@gmail.com): 添加重绘监听器。
		addPaintListener(new PaintListener() {

			@Override
			public void paintControl(PaintEvent event) {
				onPaint(event);
			}

		});
		// TODO(deathknight0718@gmail.com): 添加鼠标点击监听器。
		addMouseListener(new MouseAdapter() {

			@Override
			public void mouseDoubleClick(MouseEvent event) {
				onMouseDoubleClick(event);
			}

			@Override
			public void mouseDown(MouseEvent event) {
				onMouseDown(event);
			}

			@Override
			public void mouseUp(MouseEvent event) {
				// Nothing
			}

		});
		// TODO(deathknight0718@gmail.com): 添加鼠标悬浮监听器。
		addMouseTrackListener(new MouseTrackAdapter() {

			@Override
			public void mouseExit(MouseEvent event) {
				onMouseExit(event);
			}

			@Override
			public void mouseHover(MouseEvent event) {
				onMouseHover(event);
			}

		});
		// TODO(deathknight0718@gmail.com): 添加纵向滚动条监听器。
		vScroll.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				onVerticalScrollSelected();
			}

		});
		// TODO(deathknight0718@gmail.com): 添加横向滚动条监听器。
		hScroll.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				onHorizontalScrollSelected();
			}

		});
	}

	void initThread() {
		ganttThread = new GanttThread(this);
	}

	// ----------------------------------------------- 扩展监听模式

	public void setTransfer(Transfer[] transfer) {
		checkWidget();
		if (transfer == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		dragSource.setTransfer(transfer);
		dropTarget.setTransfer(transfer);
	}

	public void addDragListener(DragSourceListener listener) {
		checkWidget();
		if (listener == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		dragSource.addDragListener(listener);
	}

	public void addDropListener(DropTargetListener listener) {
		checkWidget();
		if (listener == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		dropTarget.addDropListener(listener);
	}

	public void addSelectionListener(SelectionListener listener) {
		checkWidget();
		if (listener == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		addListener(SWT.Selection, new TypedListener(listener));
		addListener(SWT.DefaultSelection, new TypedListener(listener));
	}

	public void addGanttListener(IGanttListener listener) {
		checkWidget();
		if (listener == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		ganttListeners.add(listener);
	}

	public void removeGanttListener(IGanttListener listener) {
		checkWidget();
		if (listener == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		ganttListeners.remove(listener);
	}

	// ----------------------------------------------- 私有监听事件引擎

	private void onPaint(PaintEvent event) {
		drawChart(event.gc, null);
	}

	private void onMouseDoubleClick(MouseEvent event) {
		Point point = new Point(event.x, event.y);
		if (headerVisible) {
			Rectangle bounds = new Rectangle(getClientArea().x, getClientArea().y, getClientArea().width, settings.getGanttMeasureLevel() * settings.getHeaderRowHeight());
			if (bounds.contains(point)) {
				GanttEvent ganttEvent = new GanttEvent(event);
				for (Iterator iter = ganttListeners.iterator(); iter.hasNext();) {
					IGanttListener ganttListener = (IGanttListener) iter.next();
					ganttListener.handleGanttHeaderDoubleClick(ganttEvent);
				}
			}
		}
		GanttItem item = getItem(point);
		GanttTask task = getTask(point);
		if (item != null && task == null) {
			GanttEvent ganttEvent = new GanttEvent(event);
			ganttEvent.item = item;
			for (Iterator iter = ganttListeners.iterator(); iter.hasNext();) {
				IGanttListener ganttListener = (IGanttListener) iter.next();
				ganttListener.handleGanttItemDoubleClick(ganttEvent);
			}
		}
	}

	private void onMouseDown(MouseEvent event) {
		Event selectionEvent = null;
		Point point = new Point(event.x, event.y);
		GanttTask task = getTask(point);
		// TODO(deathknight0718@gmail.com): 鼠标下按点击事件选中任务条。
		if (task != null && event.button == 1) {
			selectionEvent = handleSelection(task, event.stateMask);
			GanttEvent ganttEvent = new GanttEvent(event);
			for (Iterator iter = ganttListeners.iterator(); iter.hasNext();) {
				IGanttListener ganttListener = (IGanttListener) iter.next();
				ganttListener.handleGanttTaskClick(ganttEvent);
			}
		}
		// TODO(deathknight0718@gmail.com): 鼠标下按点击事件选中空白区域。
		if (task == null && event.button == 1) {
			selectedTasks.clear();
		}
		redraw();
		// TODO(deathknight0718@gmail.com): 鼠标选中事件触发。
		if (selectionEvent != null) {
			selectionEvent.stateMask = event.stateMask;
			selectionEvent.button = event.button;
			selectionEvent.item = task;
			selectionEvent.x = event.x;
			selectionEvent.y = event.y;
			notifyListeners(SWT.Selection, selectionEvent);
		}
	}

	private void onMouseExit(MouseEvent event) {
		clearTooltip();
	}

	private void onMouseHover(MouseEvent event) {
		Point point = new Point(event.x, event.y);
		GanttTask task = getTask(point);
		clearTooltip();
		if (task != null) {
			showTooltip(task, event);
		}
	}

	private void onVerticalScrollSelected() {
		redraw();
	}

	private void onHorizontalScrollSelected() {
		int middleSelection = (hScroll.getMaximum() - hScroll.getMinimum() - hScroll.getThumb()) / 2;
		int selection = hScroll.getSelection();
		int diff = selection - middleSelection;
		jointHandler.jointHorizontalMovement(diff);
		hScroll.setSelection(middleSelection);
	}

	// ----------------------------------------------- 私有组件构造器

	private void showTooltip(GanttTask task, MouseEvent event) {
		Point point = new Point(event.x, event.y);
		GanttTooltip.create(task, settings, colorManager, paintManager, toDisplay(point.x + 15, point.y + 15));
	}

	private void clearTooltip() {
		GanttTooltip.clear();
	}

	// ----------------------------------------------- 私有图形绘制器

	private void drawChart(GC gc, Rectangle bounds) {
		if (bounds == null) {
			bounds = getClientArea();
		}

		displayBounds = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
		Rectangle contentBounds = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);

		if (headerVisible) {
			drawHeader(gc);
			contentBounds.y = bounds.y + settings.getGanttMeasureLevel() * settings.getHeaderRowHeight() + 1;
			contentBounds.height = bounds.height - settings.getGanttMeasureLevel() * settings.getHeaderRowHeight();
		}

		calculateUpperCalender(contentBounds);
		calculateVerticalBar(contentBounds);

		drawContent(gc, contentBounds, false);

		drawTasks(gc, contentBounds);
		drawTimestamp(gc, contentBounds);
	}

	private void drawHeader(GC gc) {
		int rowHeight = settings.getHeaderRowHeight();
		int level = 0;
		{
			GanttMeasure measure = settings.getHeaderTopMeasure();
			if (measure != null) {
				level++;
				Rectangle bounds = new Rectangle(displayBounds.x, displayBounds.y + rowHeight * (level - 1), displayBounds.width, rowHeight);
				drawHeaderRow(gc, bounds, measure);
			}
		}
		{
			GanttMeasure measure = settings.getHeaderMiddleMeasure();
			if (measure != null) {
				level++;
				Rectangle bounds = new Rectangle(displayBounds.x, displayBounds.y + rowHeight * (level - 1), displayBounds.width, rowHeight);
				drawHeaderRow(gc, bounds, measure);
			}
		}
		{
			GanttMeasure measure = settings.getHeaderBottomMeasure();
			if (measure != null) {
				level++;
				Rectangle bounds = new Rectangle(displayBounds.x, displayBounds.y + rowHeight * (level - 1), displayBounds.width, rowHeight);
				drawHeaderRow(gc, bounds, measure);
			}
		}
		int upperY = displayBounds.y + rowHeight * level;
		gc.setForeground(colorManager.getHeaderLineColor());
		gc.drawLine(displayBounds.x, upperY, displayBounds.x + displayBounds.width, upperY);
	}

	private void drawHeaderRow(GC gc, Rectangle bounds, GanttMeasure measure) {
		int upperX = bounds.x + bounds.width;
		Font oldFont = gc.getFont();
		Font newFont = null;
		{
			gc.setBackground(colorManager.getHeaderBackground());
			gc.fillRectangle(bounds);
			gc.setForeground(colorManager.getHeaderLineColor());
			gc.drawLine(bounds.x, bounds.y, upperX, bounds.y);
			gc.drawLine(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height);
		}
		{
			FontData[] fontData = oldFont.getFontData();
			fontData[0].setHeight(8);
			newFont = new Font(Display.getDefault(), fontData);
			gc.setFont(newFont);
		}
		{
			int lineX = 0;
			int unitWidth = (int) (settings.getGanttMeasureWidth() * settings.getGanttMeasureProportion(measure));
			Calendar calendar = Calendar.getInstance(settings.getTimeZone(), settings.getLocale());
			calendar.setTime(lowerCalendar.getTime());
			lineX = bounds.x;
			while (lineX <= upperX) {
				gc.setForeground(colorManager.getHeaderTextColor());
				gc.drawString(formatDate(calendar, measure.getDateFormat()), lineX + 16, bounds.y + 3, true);
				calendar.add(measure.getUnit(), measure.getUnitCount());
				lineX += unitWidth;
				gc.setForeground(colorManager.getHeaderLineColor());
				gc.drawLine(lineX, bounds.y, lineX, bounds.y + bounds.height);
			}
			newFont.dispose();
		}
		gc.setFont(oldFont);
	}

	private void drawRanges(GC gc) {
		for (Iterator iter = ranges.iterator(); iter.hasNext();) {
			paintManager.drawRange(gc, (GanttRange) iter.next(), settings, colorManager);
		}
	}

	private void drawContent(GC gc, Rectangle bounds, boolean calculateOnly) {
		gc.setBackground(colorManager.getContentBackground());
		gc.fillRectangle(bounds);
		calculateRangeBounds(bounds);
		drawRanges(gc);
		drawContentHorizontalLine(gc, bounds);
		drawContentVerticalLine(gc, bounds);
		calculateItemBounds(bounds);
	}

	private void drawContentHorizontalLine(GC gc, Rectangle bounds) {
		int rowHeight = settings.getTaskHeight() + settings.getTaskSpacer();
		int lineY = bounds.y;
		int upperY = bounds.y + bounds.height;
		gc.setForeground(colorManager.getContentLineColor());
		while (lineY <= upperY) {
			lineY += rowHeight;
			gc.drawLine(bounds.x, lineY - 1, bounds.x + bounds.width, lineY - 1);
		}
	}

	private void drawContentVerticalLine(GC gc, Rectangle bounds) {
		int lineX = bounds.x;
		int upperX = bounds.x + bounds.width;
		int unitWidth = settings.getGanttMeasureWidth();
		gc.setForeground(colorManager.getContentLineColor());
		while (lineX <= upperX) {
			lineX += unitWidth;
			gc.drawLine(lineX, bounds.y, lineX, bounds.y + bounds.height);
		}
	}

	private void drawTasks(GC gc, Rectangle bounds) {
		for (Iterator iter = lazyTasks.iterator(); iter.hasNext();) {
			GanttTask task = (GanttTask) iter.next();
			paintManager.drawTask(gc, task, settings, colorManager, selectedTasks.contains(task));
		}
	}

	private void drawTimestamp(GC gc, Rectangle bounds) {
		Calendar calender = Calendar.getInstance(settings.getTimeZone(), settings.getLocale());
		if (calender.after(lowerCalendar) && calender.before(upperCalendar)) {
			int diff = (int) ((calender.getTimeInMillis() - lowerCalendar.getTimeInMillis()) / settings.getMeasureEquivalent());
			gc.setForeground(ColorCache.getColor(0, 255, 0));
			gc.setLineStyle(SWT.LINE_SOLID);
			gc.drawLine(bounds.x + diff, bounds.y, bounds.x + diff, bounds.y + bounds.height);
			gc.drawLine(bounds.x + diff + 1, bounds.y, bounds.x + diff + 1, bounds.y + bounds.height);
		}
	}

	// ----------------------------------------------- 私有参数计算器

	private void calculateUpperCalender(Rectangle bounds) {
		long boundsInterval = settings.getMeasureEquivalent() * bounds.width;
		upperCalendar = Calendar.getInstance(settings.getTimeZone(), settings.getLocale());
		upperCalendar.setTimeInMillis(lowerCalendar.getTimeInMillis() + boundsInterval);
	}

	private void calculateVerticalBar(Rectangle bounds) {
		visibleItems.clear();
		for (Iterator iter = items.iterator(); iter.hasNext();) {
			GanttItem item = (GanttItem) iter.next();
			if (item.isVisible()) {
				visibleItems.add(item);
			}
		}
		int rowHeight = settings.getTaskHeight() + settings.getTaskSpacer();
		int blanketHeight = rowHeight * visibleItems.size();
		if (blanketHeight > bounds.height) {
			int max = visibleItems.size() + 1;
			int selection = Math.min(vScroll.getSelection(), max);
			int thumb = bounds.height * max / blanketHeight;
			vScroll.setValues(selection, 0, max, thumb, 1, thumb);
			vScroll.setVisible(true);
		} else {
			vScroll.setVisible(false);
			vScroll.setValues(0, 0, 1, 1, 1, 1);
		}
	}

	private void calculateRangeBounds(Rectangle bounds) {
		for (Iterator iter = ranges.iterator(); iter.hasNext();) {
			GanttRange range = (GanttRange) iter.next();
			int rangeWidth = (int) ((range.getUpperCalendar().getTimeInMillis() - range.getLowerCalendar().getTimeInMillis()) / settings.getMeasureEquivalent());
			int rangeX = (int) ((range.getLowerCalendar().getTimeInMillis() - lowerCalendar.getTimeInMillis()) / settings.getMeasureEquivalent());
			Rectangle rangeBounds = new Rectangle(bounds.x + rangeX, bounds.y, rangeWidth, bounds.height);
			range.setBounds(rangeBounds);
		}
	}

	private void calculateItemBounds(Rectangle bounds) {
		int rowHeight = settings.getTaskHeight() + settings.getTaskSpacer();
		int itemSize = visibleItems.size();
		topIndex = 0;
		int bottomIndex = visibleItems.size();
		if (vScroll.getVisible()) {
			int equivalent = vScroll.getMaximum() / (visibleItems.size() + 1);
			itemSize = vScroll.getThumb() / equivalent + 1;
			topIndex = vScroll.getSelection() / equivalent;
			bottomIndex = topIndex + itemSize > visibleItems.size() ? visibleItems.size() : topIndex + itemSize;
		}
		lazyItems.clear();
		lazyTasks.clear();
		int visibleIndex = topIndex;
		for (int index = topIndex; index < bottomIndex; index++) {
			GanttItem item = (GanttItem) visibleItems.get(index);
			if (item.isVisible()) {
				Rectangle itemBounds = new Rectangle(bounds.x, bounds.y + (visibleIndex - topIndex) * rowHeight, bounds.width, rowHeight);
				item.setBounds(itemBounds);
				lazyItems.add(item);
				GanttTask[] itemTasks = item.getTasks();
				for (int i = 0; i < itemTasks.length; i++) {
					if (itemTasks[i].isIntersect(lowerCalendar, upperCalendar)) {
						calculateTaskBounds(itemTasks[i], itemBounds);
						lazyTasks.add(itemTasks[i]);
					}
				}
				visibleIndex++;
			}
		}
	}

	private void calculateTaskBounds(GanttTask task, Rectangle itemBounds) {
		int taskWidth = (int) ((task.getUpperCalendar().getTimeInMillis() - task.getLowerCalendar().getTimeInMillis()) / settings.getMeasureEquivalent());
		int taskX = (int) ((task.getLowerCalendar().getTimeInMillis() - lowerCalendar.getTimeInMillis()) / settings.getMeasureEquivalent());
		Rectangle taskBounds = new Rectangle(itemBounds.x + taskX, itemBounds.y + settings.getTaskSpacer() / 2 - 1, taskWidth, settings.getTaskHeight());
		task.setBounds(taskBounds);
	}

	private Event handleSelection(GanttTask task, int stateMask) {
		// TODO(deathknight0718@gmail.com): 默认选中模式。
		if (selectionType == SWT.SINGLE || (selectionType == SWT.MULTI && (stateMask & SWT.MOD1) == 0)) {
			selectedTasks.clear();
			selectedTasks.add(task);
		}
		// TODO(deathknight0718@gmail.com): CTRL按键选中模式。
		if (selectionType == SWT.MULTI && (stateMask & SWT.MOD1) != 0) {
			if (!selectedTasks.contains(task)) {
				selectedTasks.add(task);
			}
		}
		// TODO(deathknight0718@gmail.com): 考虑今后扩展SHIFT按键选中模式。
		return new Event();
	}

	// ----------------------------------------------- 私有对象工具箱。

	String formatDate(Calendar calendar, String format) {
		DateFormat dateFormat = new SimpleDateFormat(format, settings.getLocale());
		dateFormat.setTimeZone(settings.getTimeZone());
		return dateFormat.format(calendar.getTime());
	}

	// ----------------------------------------------- 私有属性处理器。

	void newItem(GanttItem item, int index) {
		checkWidget();
		if (index < -1 || index >= items.size()) {
			SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		}
		if (index == -1) {
			items.add(item);
		} else {
			items.add(index, item);
		}
		GanttTask[] itemTasks = item.getTasks();
		for (int i = 0; i < itemTasks.length; i++) {
			newTask(itemTasks[i], -1);
		}
		redraw();
	}

	void newTask(GanttTask task, int index) {
		checkWidget();
		if (index == -1) {
			tasks.add(task);
		}
		redraw();
	}

	public void removeTask(GanttTask task) {
		if (!tasks.contains(task)) {
			SWT.error(SWT.ERROR_CANNOT_GET_ITEM);
		}
		task.getItem().removeTask(task);
		selectedTasks.remove(task);
		lazyTasks.remove(task);
		tasks.remove(task);
		task.dispose();
		redraw();
	}

	public void removeItem(GanttItem item) {
		{
			selectedTasks.removeAll(Arrays.asList(item.getTasks()));
			lazyTasks.removeAll(Arrays.asList(item.getTasks()));
			tasks.removeAll(Arrays.asList(item.getTasks()));
		}
		{
			visibleItems.remove(item);
			lazyItems.remove(item);
			items.remove(item);
		}
		GanttTask[] itemTasks = item.getTasks();
		for (int i = 0; i < itemTasks.length; i++) {
			itemTasks[i].dispose();
		}
		redraw();
	}

	public void removeAll() {
		{
			selectedTasks.clear();
			lazyTasks.clear();
			tasks.clear();
		}
		{
			visibleItems.clear();
			lazyItems.clear();
			items.clear();
		}
		for (Iterator iter = tasks.iterator(); iter.hasNext();) {
			GanttTask task = (GanttTask) iter.next();
			task.dispose();
		}
		for (Iterator iter = items.iterator(); iter.hasNext();) {
			GanttItem item = (GanttItem) iter.next();
			item.dispose();
		}
		redraw();
	}

	public void moveHorizontal(int diff) {
		lowerCalendar.add(settings.getHeaderMinimalMeasure().getUnit(), settings.getHeaderMinimalMeasure().getUnitCount() * diff);
		redraw();
	}

	// ----------------------------------------------- 

	public void markDown() {
		checkWidget();
		for (Iterator iter = tasks.iterator(); iter.hasNext();) {
			GanttTask task = (GanttTask) iter.next();
			task.getImage().dispose();
		}
		redraw();
	}

	public void markUp(GanttTask[] tasks, Image image) {
		checkWidget();
		for (int i = 0; i < tasks.length; i++) {
			tasks[i].setImage(image);
		}
		redraw();
	}

	public void showTopIndex(int index) {
		checkWidget();
		if (index < 0 || index > items.size()) {
			SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		}
		int rowHeight = settings.getTaskHeight() + settings.getTaskSpacer();
		int blanketHeight = rowHeight * visibleItems.size();
		int selection = 0;
		if (blanketHeight > getClientArea().height) {
			selection = index * vScroll.getMaximum() / visibleItems.size();
		}
		vScroll.setSelection(selection);
		redraw();
	}

	public void showTask(GanttTask task) {
		checkWidget();
		if (!tasks.contains(task)) {
			SWT.error(SWT.ERROR_CANNOT_GET_ITEM);
		}
		selectedTasks.clear();
		selectedTasks.add(task);
		int diffX = getClientArea().width / 2;
		long timeInterval = settings.getMeasureEquivalent() * diffX;
		lowerCalendar = Calendar.getInstance(settings.getTimeZone(), settings.getLocale());
		Calendar taskLowerCalendar = task.getLowerCalendar();
		Calendar taskUpperCalendar = task.getUpperCalendar();
		lowerCalendar.setTimeInMillis(taskLowerCalendar.getTimeInMillis() - timeInterval + (taskUpperCalendar.getTimeInMillis() - taskLowerCalendar.getTimeInMillis()) / 2);
		lowerCalendar.set(Calendar.MINUTE, 0);
		lowerCalendar.set(Calendar.SECOND, 0);
		lowerCalendar.set(Calendar.MILLISECOND, 0);
		int itemIndex = visibleItems.indexOf(task.getItem());
		int rowHeight = settings.getTaskHeight() + settings.getTaskSpacer();
		int blanketHeight = rowHeight * visibleItems.size();
		int selection = 0;
		if (blanketHeight > getClientArea().height) {
			selection = itemIndex * vScroll.getMaximum() / visibleItems.size();
		}
		vScroll.setSelection(selection);
		redraw();
	}

	public void startSyncTheard() {
		ganttThread.start();
	}

	public void stopSyncTheard() {
		ganttThread.start();
	}

	// -----------------------------------------------

	@Override
	public void redraw() {
		super.redraw(getClientArea().x, getClientArea().y, getClientArea().width, getClientArea().height, false);
	}

	// ----------------------------------------------- 

	public IGanttSettings getGanttSettings() {
		return settings;
	}

	public void setGanttSettings(IGanttSettings settings) {
		checkWidget();
		this.settings = settings;
		redraw();
	}

	public IScrollBarProxy getVerticalScrollBarProxy() {
		return vScroll;
	}

	public void setVerticalScrollBarProxy(IScrollBarProxy vScroll) {
		this.vScroll = vScroll;
		vScroll.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				onVerticalScrollSelected();
			}

		});
	}

	public IGanttJointHandler getJointHandler() {
		return jointHandler;
	}

	public void setJointHandler(IGanttJointHandler jointHandler) {
		this.jointHandler = jointHandler;
	}

	public Calendar getLowerCalendar() {
		return lowerCalendar;
	}

	public void setLowerCalendar(Calendar lowerCalendar) {
		this.lowerCalendar = lowerCalendar;
	}

	public GanttTask[] getSelection() {
		return (GanttTask[]) selectedTasks.toArray(new GanttTask[selectedTasks.size()]);
	}

	public void setSelection(GanttTask task) {
		checkWidget();
		if (task == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		selectedTasks.clear();
		selectedTasks.add(task);
		redraw();
	}

	public void setSelection(GanttTask[] tasks) {
		checkWidget();
		if (tasks == null) {
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		}
		selectedTasks.clear();
		selectedTasks.addAll(Arrays.asList(tasks));
		redraw();
	}

	public boolean isHeaderVisible() {
		return headerVisible;
	}

	public void setHeaderVisible(boolean headerVisible) {
		checkWidget();
		this.headerVisible = headerVisible;
		redraw();
	}

	// ----------------------------------------------- 

	public GanttTask getTask(Point point) {
		checkWidget();
		for (Iterator iter = lazyTasks.iterator(); iter.hasNext();) {
			GanttTask task = (GanttTask) iter.next();
			if (task.getBounds().contains(point)) {
				return task;
			}
		}
		return null;
	}

	public GanttTask[] getTasks() {
		checkWidget();
		return (GanttTask[]) tasks.toArray(new GanttTask[tasks.size()]);
	}

	public GanttItem getItem(Point point) {
		checkWidget();
		for (Iterator iter = lazyItems.iterator(); iter.hasNext();) {
			GanttItem item = (GanttItem) iter.next();
			if (item.getBounds().contains(point)) {
				return item;
			}
		}
		return null;
	}

	public GanttItem[] getItems() {
		checkWidget();
		return (GanttItem[]) items.toArray(new GanttItem[items.size()]);
	}

	public int getItemCount() {
		checkWidget();
		return items.size();
	}

}
